#ifndef __BANDWITHLIMITSENDPROXY_H__
#define __BANDWITHLIMITSENDPROXY_H__

#include "TcpTunnel.h"

namespace encrypted 
{
    namespace tunnel
    {

        template<typename CryptoPtr>
        class BandwithLimitSendProxy
        {
        public:
            typedef boost::shared_ptr<BandwithLimitSendProxy<CryptoPtr> > BandwithLimitSendProxyPtr;
            BandwithLimitSendProxy(boost::asio::io_service& io_service,
                                   size_t limitBandwith)
                : timer(io_service),
                  offset(0),
                  sentBytes(0),
                  limitBandwith(limitBandwith)
            {
            }

            ~BandwithLimitSendProxy()
            {
            }

            // Start a timer, will call BandwithLimitSendProxy::interval every 100 microseconds
            inline void start()
            {
                timer.expires_from_now(boost::posix_time::millisec(100));
                timer.async_wait(boost::bind(&BandwithLimitSendProxy::interval, this, _1));
            }

            // Proxy the send action from a Tunnel(such as TcpTunnel)
            inline void send(TcpSocketPtr peerout, char* buf, size_t bufLen,
                             TcpTunnel<CryptoPtr, BandwithLimitSendProxyPtr>* src)
            {
                sendQueue.push(SendParam(peerout, buf, bufLen, src));
                sp_send();
            }
        private:

            void sp_send()
            {
                while(sentBytes < limitBandwith && !sendQueue.empty())
                {
                    SendParam sendParam = sendQueue.front();
                    sendParam.peerout->async_send(boost::asio::buffer(sendParam.buf, sendParam.bufLen), \
                                                  boost::bind(&TcpTunnel<CryptoPtr, BandwithLimitSendProxyPtr>::sent,\
                                                              sendParam.src, _1, _2));
                    sentBytes += sendParam.bufLen;
                    packetsLen.insert(std::pair<size_t, size_t>(offset, sendParam.bufLen));
                    sendQueue.pop();
                }
            }

            // Remove the sent bytes before one second, and call doSend()
            void interval(const boost::system::error_code& error)
            {
                ++offset;

                for(std::multimap<size_t, size_t>::const_iterator it = packetsLen.find(offset - 10);\
                    it != packetsLen.end() && it->first == offset - 10; ++it)
                {
                    sentBytes -= it->second;
                }

                packetsLen.erase(offset - 10);
                sp_send();
                start();
            }

            struct SendParam
            {
                TcpSocketPtr peerout;
                char* buf;
                size_t bufLen;
                TcpTunnel<CryptoPtr, BandwithLimitSendProxyPtr>* src;
                SendParam( TcpSocketPtr peerout,
                           char* buf,
                           size_t bufLen,
                           TcpTunnel<CryptoPtr, BandwithLimitSendProxyPtr>* src)
                    : peerout(peerout), buf(buf), bufLen(bufLen), src(src)
                {
                }
            };

            std::multimap<size_t, size_t> packetsLen;
            std::queue<SendParam> sendQueue;
            size_t offset;
            size_t sentBytes;
            size_t limitBandwith;
            boost::asio::deadline_timer timer;
        };
    }
}
#endif
